﻿namespace EasyToolkit.Excel.BindingTemplates.Renderer
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    using System.Threading.Tasks;
    using EasyToolkit.BindingTemplates.Context;
    using EasyToolkit.Excel.Application;
    using EasyToolkit.Excel.Application.Async;
    using EasyToolkit.Excel.BindingTemplates.Controls.Button.SortAndFilter;
    using Microsoft.Office.Interop.Excel;
    
    class ExcelRenderer : IDisposable
    {
        #region properties
        private ExcelTemplateView view;
        private ExcelSortAndFilterButton sortAndFilterButton;
        private ExcelPartRenderer partRenderer;
        private IBindingContextItem[,] contextItems;
        private object[,] cells;

        public bool IsDisposed
        { get; private set; }

        public Range OutputRange
        { get; private set; }
        #endregion

        #region .ctors 
        public ExcelRenderer(ExcelTemplateView view)
        {
            this.view = view;
            sortAndFilterButton = null;// new ExcelSortAndFilterButton(view);
        }
        #endregion

        #region public methods
        public void Clear()
        {
            if (!IsDisposed && OutputRange != null)
            {
                using (FreezeExcel freezeExcel = new FreezeExcel(view.FirstOutputCell.Application))
                {
                    if (view.ClearingCell != null)
                        view.ClearingCell.Copy(OutputRange);
                    else
                        OutputRange.Clear();
 
                    OutputRange = null;
                    contextItems = null;
                    cells = null;
                    partRenderer.Dispose();
                    partRenderer = null;
                }
            }
        }

        public void Render()
        {
            if (!IsDisposed)
            {
                partRenderer = new ExcelPartRenderer(view);
                partRenderer.Render();
                OutputRange = partRenderer.OutputRange;
                if (OutputRange != null)
                {
                    contextItems = new IBindingContextItem[OutputRange.Rows.Count, OutputRange.Columns.Count];
                    cells = new object[OutputRange.Rows.Count, OutputRange.Columns.Count];
                    RenderData();

                    BorderAround(OutputRange, XlLineStyle.xlContinuous, XlBorderWeight.xlThick);
                }
           }
        }

        public void RenderDataOnly()
        {
            if (OutputRange != null)
                RenderData();
        }

        public void OnDataChanged(Range target)
        {
            if (!IsDisposed && contextItems != null)
            {
                foreach (Range cell in target.Cells)
                {
                    IBindingContextItem contextItem = contextItems[cell.Row - view.FirstOutputCell.Row, cell.Column - view.FirstOutputCell.Column];
                    if (contextItem != null)
                    {
                        object retValue;
                        bool update = contextItem.UpdateDataSource(cell.Value2, out retValue);
                        if (update && ! object.Equals(cell.Value2, retValue))
                            cell.Value2 = retValue;
                    }
                }
            }
        }

        public IBindingContextItem GetConcernedContextItem(Range target)
        {
            IBindingContextItem ret = null;
            if (target.Cells.Count == 1)
            {
                if (!IsDisposed && contextItems != null)
                    ret = contextItems[target.Row - view.FirstOutputCell.Row, target.Column - view.FirstOutputCell.Column];
            }
            return ret;
        }

        public void Dispose()
        {
            if (! IsDisposed)
            {
                Clear();
                if (sortAndFilterButton != null)
                    sortAndFilterButton.Dispose();
                IsDisposed = true;
            }
        }
        #endregion

        #region private methods
        private void RenderData()
        { 
            Parallel.For(0, partRenderer.DataRows.Count, i => { int ii = 0;
                                                            partRenderer.DataRows[i].ForEach(c => { 
                                                                                                contextItems[i, ii] = c;
                                                                                                if (c!= null)
                                                                                                {
                                                                                                    if (c.CanNotify)
                                                                                                    {
                                                                                                        ((IBindingContextItemCanNotify)c).OnPropertyChangedAction = OnNotifyPropertyChanged;
                                                                                                        ((IBindingContextItemCanNotify)c).OnPropertyChangedActionArgs = new KeyValuePair<int, int>(i, ii);
                                                                                                    }
                                                                                                    cells[i, ii++] = c.ResolvedBinding;
                                                                                                }
                                                                                                else 
                                                                                                    cells[i, ii++] = null; 
                                                                                              });
                                                          });
            OutputRange.Value2 = cells;
        }

        private void BorderAround(Range range, XlLineStyle lineStyle, XlBorderWeight Weight)
        {
            Borders borders = range.Borders;
            borders[XlBordersIndex.xlEdgeLeft].LineStyle = lineStyle;
            borders[XlBordersIndex.xlEdgeTop].LineStyle = lineStyle;
            borders[XlBordersIndex.xlEdgeBottom].LineStyle = lineStyle;
            borders[XlBordersIndex.xlEdgeRight].LineStyle = lineStyle;

            borders[XlBordersIndex.xlEdgeLeft].Weight = Weight;
            borders[XlBordersIndex.xlEdgeTop].Weight = Weight;
            borders[XlBordersIndex.xlEdgeBottom].Weight = Weight;
            borders[XlBordersIndex.xlEdgeRight].Weight = Weight;
            
            ////borders.Color = color;
            Marshal.ReleaseComObject(borders);
            borders = null;
        }

        private void OnNotifyPropertyChanged(IBindingContextItem contextItem, object param)
        {
            if (view != null && view.FirstOutputCell != null)
                ((ExcelBindingTemplateManager)ExcelBindingTemplateManager.Instance).ExcelNotifyPropertyManager.Notify(new NotityPropertyContext(contextItem, this.view, (KeyValuePair<int, int>)param));
        }
        #endregion
    }
}
